/**
 * Server Function Hub - WebSocket Server for Dynamic Function Registration and Execution
 * 
 * This server allows React components to register and execute server-side code
 * directly from client-side components without preprocessing.
 */
const WebSocket = require('ws');
const https = require('https');
const http = require('http');
const fs = require('fs');
const path = require('path');
const vm = require('vm'); // For safer function execution
require('dotenv').config();

// Configuration
const WS_PORT = process.env.WS_PORT || 8080;
const USE_SSL = process.env.USE_SSL === 'true';
const SSL_PREFIX = process.env.SSL_PREFIX || '/etc/letsencrypt/live/';
const DOMAIN = process.env.DOMAIN || 'sykotic1.com';

// SSL Certificate paths (only needed if USE_SSL is true)
const SSL_KEY_PATH = process.env.SSL_KEY_PATH || path.join(SSL_PREFIX, DOMAIN, 'privkey.pem');
const SSL_CERT_PATH = process.env.SSL_CERT_PATH || path.join(SSL_PREFIX, DOMAIN, 'fullchain.pem');

// Enhanced logging function
function log(message, details = null) {
  const timestamp = new Date().toISOString();
  console.log(`[${timestamp}] [SERVER-FUNCTION-HUB] ${message}`);
  if (details) {
    console.log(JSON.stringify(details, null, 2));
  }
}

/**
 * Store for dynamically registered functions
 * Maps function IDs to their executable representations
 */
const dynamicFunctions = new Map();

/**
 * Function registration counter for stats
 */
let registrationCount = 0;

/**
 * Connection and message stats
 */
const stats = {
  totalConnections: 0,
  activeConnections: 0,
  messageCount: 0,
  registrationCount: 0,
  executionCount: 0,
  errorCount: 0
};

/**
 * Register a function in the dynamic function registry
 */
function registerFunction(functionId, functionBody, paramNames = []) {
  registrationCount++;
  stats.registrationCount++;
  
  log(`Registering function: ${functionId}`, {
    paramCount: paramNames.length,
    bodyPreview: functionBody.substring(0, 100) + (functionBody.length > 100 ? '...' : '')
  });
  
  try {
    // Create a function from the provided body and parameters
    // We use the vm module for slightly safer execution
    const contextObject = {};
    
    // Create the function context with access to required Node.js modules
    const sandbox = {
      require,
      console,
      process: {
        env: process.env
      },
      Buffer,
      setTimeout,
      clearTimeout,
      setInterval,
      clearInterval,
      contextObject
    };
    
    // Create the function wrapper that will run the code in a VM context
    const functionWrapper = function(...args) {
      // Last argument is the context passed from the server
      const executionContext = args.pop() || {};
      
      // Create the function to execute
      const functionCode = `
        (async function(${paramNames.join(', ')}) {
          ${functionBody}
        })
      `;
      
      // Execute the function in the VM context
      const vmContext = vm.createContext(sandbox);
      const script = new vm.Script(functionCode);
      const fn = script.runInContext(vmContext);
      
      // Call the function with the provided arguments
      return fn(...args);
    };
    
    // Store the function in the registry
    dynamicFunctions.set(functionId, functionWrapper);
    
    return true;
  } catch (error) {
    log(`Error registering function ${functionId}:`, {
      error: error.message,
      stack: error.stack
    });
    
    stats.errorCount++;
    return false;
  }
}

/**
 * Execute a previously registered function
 */
async function executeFunction(functionId, context, args = []) {
  stats.executionCount++;
  
  log(`Executing function: ${functionId}`, {
    args: args.map(arg => 
      typeof arg === 'object' ? '(object)' : 
      typeof arg === 'string' && arg.length > 100 ? `${arg.substring(0, 100)}...` : arg
    )
  });
  
  // Check if the function exists
  if (!dynamicFunctions.has(functionId)) {
    throw new Error(`Function not found: ${functionId}`);
  }
  
  try {
    // Get the function from the registry
    const func = dynamicFunctions.get(functionId);
    
    // Execute it with the provided arguments
    const result = await func(...args, context);
    
    log(`Function executed successfully: ${functionId}`);
    return result;
  } catch (error) {
    log(`Error executing function ${functionId}:`, {
      error: error.message,
      stack: error.stack
    });
    
    stats.errorCount++;
    throw error;
  }
}

/**
 * Start the WebSocket server
 */
function startServer() {
  try {
    let server;
    
    // Create HTTP/HTTPS server based on configuration
    if (USE_SSL) {
      log('Starting with SSL enabled');
      server = https.createServer({
        key: fs.readFileSync(SSL_KEY_PATH),
        cert: fs.readFileSync(SSL_CERT_PATH)
      });
    } else {
      log('Starting without SSL (development mode)');
      server = http.createServer();
    }
    
    // Create WebSocket server
    const wss = new WebSocket.Server({ 
      server,
      path: '/ws'
    });
    
    // Log when server starts
    wss.on('listening', () => {
      log(`WebSocket server is listening on port ${WS_PORT} (${USE_SSL ? 'with SSL' : 'no SSL'})`);
    });
    
    // Log server errors
    wss.on('error', (error) => {
      stats.errorCount++;
      log('WebSocket server error:', {
        error: error.message,
        stack: error.stack
      });
    });
    
    // Handle WebSocket connections
    wss.on('connection', (ws, req) => {
      stats.totalConnections++;
      stats.activeConnections++;
      
      const clientIp = req.socket.remoteAddress;
      const connectionId = `conn-${Date.now()}-${stats.totalConnections}`;
      
      log('New WebSocket connection established', {
        connectionId,
        clientIp,
        path: req.url
      });
      
      // Store connection-specific state
      const connectionState = {
        id: connectionId,
        registeredFunctions: new Set()
      };
      
      // Handle connection close
      ws.on('close', () => {
        stats.activeConnections--;
        log('WebSocket connection closed', {
          connectionId,
          stats: { ...stats }
        });
      });
      
      // Handle errors
      ws.on('error', (error) => {
        stats.errorCount++;
        log('WebSocket connection error', {
          connectionId,
          error: error.message
        });
      });
      
      // Process incoming messages
      ws.on('message', async (message) => {
        stats.messageCount++;
        let requestId = 'unknown';
        
        try {
          // Parse the message
          const messageStr = message.toString();
          const request = JSON.parse(messageStr);
          requestId = request.requestId || 'unknown';
          
          log(`Received request:`, {
            connectionId,
            requestId,
            type: request.type
          });
          
          // Handle function registration
          if (request.type === 'register') {
            const { functionId, functionBody, paramNames = [] } = request;
            
            const success = registerFunction(functionId, functionBody, paramNames);
            
            if (success) {
              connectionState.registeredFunctions.add(functionId);
            }
            
            ws.send(JSON.stringify({
              requestId,
              result: { 
                registered: success, 
                functionId 
              }
            }));
            return;
          }
          
          // Handle function execution
          if (request.type === 'execute') {
            const { functionId, args = [] } = request;
            
            try {
              // Create an execution context
              const context = {
                clientIp,
                connectionId,
                requestId,
                timestamp: new Date().toISOString()
              };
              
              // Execute the function
              const result = await executeFunction(functionId, context, args);
              
              // Send result back to client
              ws.send(JSON.stringify({
                requestId,
                result
              }));
            } catch (error) {
              log(`Error executing function:`, {
                connectionId,
                requestId,
                functionId,
                error: error.message
              });
              
              ws.send(JSON.stringify({
                requestId,
                error: error.message
              }));
            }
            return;
          }
          
          // Handle unknown request types
          log(`Unknown request type: ${request.type}`, {
            connectionId,
            requestId
          });
          
          ws.send(JSON.stringify({
            requestId,
            error: `Unknown request type: ${request.type}`
          }));
        } catch (error) {
          log('Message parsing error:', {
            connectionId,
            error: error.message,
            messagePreview: typeof message === 'string' ? 
              message.substring(0, 200) : 'Non-string message'
          });
          
          stats.errorCount++;
          
          ws.send(JSON.stringify({
            requestId,
            error: 'Invalid message format'
          }));
        }
      });
      
      // Send welcome message
      ws.send(JSON.stringify({
        type: 'system',
        message: 'Server function hub connected',
        connectionId,
        timestamp: new Date().toISOString()
      }));
    });
    
    // Start the server
    server.listen(WS_PORT, () => {
      log(`Server listening on port ${WS_PORT}`);
    });
    
    // Log stats periodically
    setInterval(() => {
      if (stats.totalConnections > 0) {
        log('Server stats', {
          ...stats,
          dynamicFunctionsCount: dynamicFunctions.size
        });
      }
    }, 60000);
    
    return {
      server: wss,
      registerFunction,
      executeFunction,
      stats: () => ({ ...stats })
    };
  } catch (error) {
    log('FATAL ERROR starting server:', {
      error: error.message,
      stack: error.stack
    });
    
    // Check for SSL errors
    if (USE_SSL && error.code === 'ENOENT' && error.message.includes('ENOENT')) {
      log('SSL certificate files not found. Please check paths:', {
        keyPath: SSL_KEY_PATH,
        certPath: SSL_CERT_PATH
      });
      
      log('You can disable SSL for development with USE_SSL=false');
    }
    
    throw error;
  }
}

// Register a test function
registerFunction('test.hello', `
  // This is a test server function
  const name = arguments[0] || 'World';
  return {
    message: \`Hello, \${name}!\`,
    timestamp: new Date().toISOString(),
    pid: process.pid,
    nodeVersion: process.version
  };
`, ['name']);

// Export the module
module.exports = {
  startServer,
  registerFunction,
  executeFunction,
  stats: () => ({ ...stats })
};

// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
  log('UNCAUGHT EXCEPTION:', {
    error: error.message,
    stack: error.stack
  });
});

// Handle promise rejections
process.on('unhandledRejection', (reason) => {
  log('UNHANDLED PROMISE REJECTION:', {
    reason: reason ? (reason.stack || reason.message || reason) : 'Unknown reason'
  });
});

// Start the server if this file is run directly
if (require.main === module) {
  log('Starting server function hub in standalone mode');
  startServer();
  
  log('Server ready to accept function registrations and executions');
}